----------------------------------------------------------------------------------
-- Company:        RIT
-- Engineer:       Sam Skalicky
-- 
-- Create Date:    17:49:05 12/11/2009 
-- Design Name:    MSD P10662
-- Module Name:    Device - Behavioral 
-- Project Name:   Inout
-- Target Devices: Spartan 6 LXT
-- Tool versions:  QuestaSim-64 6.4c
-- Description:    This a bi-directional I2C/Two-wire port implementation in VHDL
--
-- Notes: Data is the bi-directional port, Clk is typeset as an inout in the idea that
-- the clock could be generated to this port (Master device) or recieved from this port
-- in the case of a Slave device. However it is not meant to be a bi-directional port
--
-- Dependencies:   None
--
-- Revision: 1.0
-- Revision 0.01 - File Created
-- Additional Comments: 
--
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity I2Cmaster is
    Generic (register_data_width : natural;
             register_id_width : natural);
    Port ( PS2_Clk : in  STD_LOGIC;
           FPGAClk : in std_logic;
			  I2C_Clk_out : out std_logic;
           Send : in std_logic;
           Instruction : in std_logic_vector(7 downto 0);
           register_id : in std_logic_vector(register_id_width-1 downto 0);
           Data : in std_logic_vector(register_data_width-1 downto 0);
			  I2C_Dir : out std_logic;
			  I2C_InFromBus : in std_logic;
			  I2C_OutToBus : out std_logic);
end I2Cmaster;

architecture Behavioral of I2Cmaster is

	signal Dir,out_i2c,in_i2c,out_i2cclk : std_logic:='1';
	type states is (idle,start,sendAddress,sendReg,sendData,recieve,stop,ack,recAck);
  signal pstate,nstate : states:=idle;
  signal counter : std_logic_vector(4 downto 0):=(others=>'0');
  signal shiftReg,i2cAddr : std_logic_vector(7 downto 0):="00000000";
  signal rID : std_logic_vector(register_id_width-1 downto 0):=(others=>'0');
  signal sData : std_logic_vector(register_data_width-1 downto 0):=(others=>'0');
  signal writeCount : natural:=0;
  signal ack_count : std_logic_vector(11 downto 0):=(others=>'0');
  signal delay_count : std_logic_vector(15 downto 0):=(others=>'0');
  signal ClkEdge : std_logic_vector(1 downto 0):="00";
  signal ClkFallingEdge,ClkRisingEdge,prevClk,sSend : std_logic:='0';
  signal byteCount : std_logic_vector(7 downto 0);
  
begin
  
  sData<=Data;
  sSend<=Send;
  i2cAddr<=Instruction;
  rID<=register_id;
  I2C_Clk_out <= out_i2cclk;
  
  I2C_Dir <= Dir;
  out_i2c<=I2C_InFromBus;
  I2C_OutToBus<=in_i2c;

  ------------------------------
  -- I2C Clk edge detector
  ------------------------------
  process(PS2_Clk,FPGAClk)
  begin
    if rising_edge(FPGAClk) then
	   ClkEdge<=ClkEdge(0) & PS2_Clk;
		
		if ClkEdge = "00" and prevClk = '1' then
		  ClkFallingEdge <= '1';
		  ClkRisingEdge<='0';
		  prevClk<='0';
		elsif ClkEdge = "11" and prevClk = '0' then
		  prevClk<='1';
		  ClkFallingEdge<='0';
		  ClkRisingEdge<='1';
		else
		  ClkFallingEdge<='0';
		  ClkRisingEdge<='0';
		end if;
	 end if;
  end process;

  ------------------------------
  -- Control
  ------------------------------
  process (FPGAClk,PS2_Clk,nstate)
  begin
    if rising_edge(FPGAClk) then
      
    --state machine
	   case nstate is
		   when idle =>	--state changes when start bit received
		     byteCount<=X"00";
		     if sSend = '1' then
			     if PS2_Clk='0' then
				     nstate<=start;
				     pstate<=nstate;
				   end if;
			   end if;
			 when start =>
			   if ack_count > X"08D" then
 		       nstate<=sendAddress;
 		       pstate<=nstate;
 		     end if;
			 when sendAddress =>
			   if delay_count > X"06D3" then
 		       nstate<=recAck;
 		       pstate<=nstate;
 		     end if;
 		   when sendReg =>
 		     if delay_count > X"06D3" then
 		       nstate<=recAck;
 		       pstate<=nstate;
 		     end if;
 		   when sendData =>
 		     if delay_count > X"06D3" then
 		       nstate<=recAck;
 		       pstate<=nstate;
 		     end if;
		   when recieve =>		--receive 8 bits
		       if counter = "01000" then
		       nstate<=ack;
		       pstate<=nstate;
		       end if;
		   when ack =>
		     if ack_count = X"1DF" then
      		       nstate<=stop;
      		       pstate<=nstate;
		     end if;
		   when recAck =>		     
		     if delay_count > X"0163" then
		       if pstate = sendAddress then
		         nstate<=sendReg;
		         pstate<=nstate;
		       elsif pstate = sendReg then
		         if i2cAddr(0) = '1' then
		           nstate<=recieve;
		           pstate<=nstate;
		         else 
		           nstate<=sendData;
		           pstate<=nstate;
		         end if;
		       else
		         if byteCount = X"00" then
		           nstate<=sendData;
		           pstate<=nstate;
    		         byteCount<=byteCount+1;
 		         else
 		           nstate<=stop;
    		         pstate<=nstate;
 		         end if;
		       end if;	       
         end if;
		 when stop =>
		   if ack_count = X"079" then
		     nstate<=idle;
		     pstate<=nstate;
		   end if;
	   end case;
	  end if;                         
  end process;
  ------------------------------
  
  
  ------------
  --Datapath
  ------------
  process (FPGAClk,nstate,PS2_Clk,counter,shiftReg)
    
  begin
   if rising_edge(FPGAClk) then
    case nstate is
       when idle =>
			  out_i2cclk<='1';
   	     counter<="00000";
   	     shiftReg<=i2cAddr;
			  Dir<='1';
	     when start =>
	       if ClkRisingEdge = '1' then
		       ack_count<=X"001";
		     end if;
		     
			 case ack_count is
				when X"000" =>
				  Dir<='0';
				  in_i2c<='0';
				when X"001" =>
				  out_i2cclk<='0';
				  ack_count<=ack_count + 1;
				when X"080" =>
				  out_i2cclk<=PS2_Clk;
				  ack_count<=ack_count + 1;
				when X"08E" =>
				  out_i2cclk<=PS2_Clk;
				  --in_i2c<='1';
				  Dir<='1';
				  ack_count<=ack_count+1;
				--when X"1E0" =>
				--  ack_count<=X"000";
				when others => 
				  ack_count<=ack_count+1;
			 end case;
	     when sendAddress =>
	       out_i2cclk<=PS2_Clk;
	       ack_count<=X"000";
	       if ClkFallingEdge = '1' then
    		       delay_count<=X"0001";
    		       writeCount<=writeCount + 1;
    		       in_i2c<=shiftReg(7);
    		       shiftReg<=shiftReg(6 downto 0)&'0';
 		     end if;		
		     
		       case delay_count is
		         when X"0000" => 
		           Dir<='0';
		           if writeCount = 0 then
		             in_i2c<=shiftReg(7);
    		           writeCount<=writeCount + 1;
		             shiftReg<=shiftReg(6 downto 0)&'0';
		           end if;
		         when X"0020" =>
		           Dir<='0';
		           delay_count<=delay_count + 1;
		         when X"06D3" =>
					  --in_i2c<='1';
		           Dir<='1';
		           delay_count<=delay_count + 1;
		           shiftReg<=rID;
		         when X"06D4" =>
		           delay_count<=X"0000";
		         when others =>
		           delay_count<=delay_count + 1;
		       end case;
		   when sendReg =>
		    out_i2cclk<=PS2_Clk;
	       ack_count<=X"000";
	       if ClkFallingEdge = '1' then
				 delay_count<=X"0001";
				 writeCount<=writeCount + 1;
				 in_i2c<=shiftReg(7);
				 shiftReg<=shiftReg(6 downto 0)&'0';
 		     end if;		
		     
			 case delay_count is
				when X"0000" => 
				  Dir<='0';
				  if writeCount = 0 then
					 in_i2c<=shiftReg(7);
					  writeCount<=writeCount + 1;
					 shiftReg<=shiftReg(6 downto 0)&'0';
				  end if;
				when X"0020" =>
				  Dir<='0';
				  delay_count<=delay_count + 1;
				when X"06D3" =>
				  --in_i2c<='1';
				  Dir<='1';
				  delay_count<=delay_count + 1;
				  shiftReg<=sData(15 downto 8);
				when X"06D4" =>
				  delay_count<=X"0000";
				when others =>
				  delay_count<=delay_count + 1;
			 end case;
			 
		   when sendData =>
		    out_i2cclk<=PS2_Clk;
	       ack_count<=X"000";
	       if ClkFallingEdge = '1' then
				 delay_count<=X"0001";
				 writeCount<=writeCount + 1;
				 in_i2c<=shiftReg(7);
				 shiftReg<=shiftReg(6 downto 0)&'0';
 		     end if;	
			 case delay_count is
				when X"0000" => 
				  Dir<='0';
				  if writeCount = 0 then
					 in_i2c<=shiftReg(7);
					  writeCount<=writeCount + 1;
					 shiftReg<=shiftReg(6 downto 0) & '0';
				  end if;
				when X"0020" =>
				  Dir<='0';
				  delay_count<=delay_count + 1;
				when X"06D3" =>
				  --in_i2c<='1';
				  Dir<='1';
				  delay_count<=delay_count + 1;
				  shiftReg<=sData(7 downto 0);
				when X"06D4" =>
				  delay_count<=X"0000";
				when others =>
				  delay_count<=delay_count + 1;
			 end case;
			 
		   when recieve =>		--receive 8 bits
			  Dir<='1';
		     out_i2cclk<=PS2_Clk;
		     ack_count<=X"000";
		     if ClkRisingEdge ='1' then
		       counter<=counter + 1;
		       shiftReg<=shiftReg(6 downto 0) & out_i2c;
		     end if;
		   when ack =>
		     out_i2cclk<=PS2_Clk;
		     if ClkFallingEdge = '1' then
		       ack_count<=X"001";
		     end if;
		     if counter = "10000" then
		     counter <= "10001";
		     end if;
		       case ack_count is
		         when X"000" => null;
		         when X"002" =>
		           Dir<='0';
		           in_i2c<='0';
		           ack_count<=ack_count + 1;
		         when X"1DF" =>
					  --in_i2c<='1';
		           Dir<='1';
		           ack_count<=X"000";
		         when X"1E0" =>
		           ack_count<=X"000";
		         when others =>
		           ack_count<= ack_count + 1;  
		       end case;
		   when recAck =>
			  Dir<='1';
		     out_i2cclk<=PS2_Clk;
		     writeCount<=0;
		     if delay_count < X"0164" then
           delay_count<=delay_count + 1;
         else
           delay_count<=X"0000";
         end if;
       when stop =>
         out_i2cclk<=PS2_Clk;
         if ClkRisingEdge = '1' then
		       ack_count<=X"001";
		     end if;
		     
			 case ack_count is
				when X"000" => 
				  Dir<='0';
				  in_i2c<='0';
				  out_i2cclk<=PS2_Clk;
				when X"002" =>
				  Dir<='0';
				  in_i2c<='0';
				  ack_count<=ack_count + 1;
				  out_i2cclk<=PS2_Clk;
				when X"040" =>
				  in_i2c<='1';
				  ack_count<=ack_count + 1;
				  out_i2cclk<=PS2_Clk;
				when X"078" =>
				  --in_i2c<='1';
				  Dir<='1';
				  ack_count<=ack_count+1;
				  out_i2cclk<='1';
				when X"079" =>
				  ack_count<=X"000";
				  out_i2cclk<='1';		       
				when others =>
				  ack_count<= ack_count + 1;  
				  out_i2cclk<=PS2_Clk;
			 end case;
	   end case;
	  end if; 
  end process;
end Behavioral;